'use client';

import { useToast } from '@/hooks/use-toast';
import { cn } from '@/lib/utils';
import { useCosClient } from '@/providers/cos-provider';
import type { TaskId } from 'cos-js-sdk-v5';
import { useId, useRef, useState, useTransition } from 'react';
import { Button } from '../ui/button';
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from '../ui/dialog';

type UploadStatus = number | 'success' | 'error' | 'wait';

export default function ImageUploader({
  onUploaded,
  genImageKey,
  button,
  single = false,
}: {
  onUploaded: (keys: string[]) => unknown;
  genImageKey: () => string;
  button: React.ReactNode;
  single?: boolean;
}) {
  const filesMapRef = useRef<Map<string, { file: File; status: UploadStatus }>>(
    new Map(),
  );
  const [_, update] = useState(0);
  const triggerUpdate = () => update((v) => (v + 1) % 10);
  const [isPending, startTransition] = useTransition();
  const [open, setOpen] = useState(false);
  const formId = useId();
  const [isDragging, setIsDragging] = useState(false);
  const pendingTasksRef = useRef<Map<string, TaskId>>(new Map());
  const cosClient = useCosClient();
  const { toast } = useToast();

  const handleDrag = (e: React.DragEvent, isDragging: boolean) => {
    e.preventDefault();
    e.stopPropagation();
    setIsDragging(isDragging);
  };

  const addFiles = (files: File[]) => {
    if (!files.length) return false;
    if (single && (files.length > 1 || filesMapRef.current.size >= 1)) {
      return toast({
        variant: 'destructive',
        title: '只能上传一个文件',
      });
    }
    const imageFiles = files.filter((file) => file.type.startsWith('image/'));
    const nameArr = imageFiles.map((file) => file.name);
    const nameSet = new Set(nameArr);
    if (nameSet.size < nameArr.length) {
      return toast({
        variant: 'destructive',
        title: '存在重名文件',
        description: '请勿上传同名文件',
      });
    }
    if (nameArr.some((i) => filesMapRef.current.has(i))) {
      return toast({
        variant: 'destructive',
        title: '文件已存在',
        description: '请勿上传同名文件',
      });
    }
    for (const file of imageFiles) {
      filesMapRef.current.set(file.name, {
        file,
        status: 'wait',
      });
    }
    triggerUpdate();
  };

  const handleUpload = () => {
    if (!cosClient)
      return toast({
        title: '获取上传客户端失败',
        variant: 'destructive',
      });
    if (!filesMapRef.current.size)
      return toast({ title: '请选择文件', variant: 'destructive' });
    startTransition(async () => {
      const fileObjs = filesMapRef.current.values().filter((f) => {
        if (f.status === 'wait' || f.status === 'error') {
          f.status = 0;
          return true;
        }
        return false;
      });
      triggerUpdate();
      const successKeys: string[] = [];
      await Promise.all(
        fileObjs.map((obj) => {
          const key = genImageKey();
          return cosClient
            .upload({
              key,
              file: obj.file,
              onProgress(info) {
                obj.status = info.percent * 100;
                triggerUpdate();
              },
              onTaskReady(id) {
                pendingTasksRef.current.set(key, id);
              },
            })
            .then(() => {
              obj.status = 'success';
              pendingTasksRef.current.delete(key);
              successKeys.push(key);
              triggerUpdate();
            })
            .catch(() => {
              obj.status = 'error';
              pendingTasksRef.current.delete(key);
              triggerUpdate();
            });
        }),
      ).then(() => {
        onUploaded(successKeys);
      });
    });
  };

  return (
    <Dialog open={open}>
      <DialogTrigger asChild onClick={() => setOpen(true)}>
        {button}
      </DialogTrigger>
      <DialogContent>
        <DialogHeader className="space-y-4">
          <DialogTitle>图片上传</DialogTitle>
          <DialogDescription>
            {single ? '选择一张图片' : '选择一张或多张图片'}
          </DialogDescription>
        </DialogHeader>
        <div
          className={cn(
            'border-2 border-dashed rounded-lg p-8 text-center transition-colors',
            isDragging
              ? 'border-primary bg-primary/10'
              : 'border-muted-foreground/25',
          )}
          onDragEnter={(e: React.DragEvent) => handleDrag(e, true)}
          onDragLeave={(e: React.DragEvent) => handleDrag(e, false)}
          onDragOver={(e: React.DragEvent) => handleDrag(e, true)}
          onDrop={(e: React.DragEvent) => {
            handleDrag(e, false);
            addFiles(Array.from(e.dataTransfer.files));
          }}
        >
          <input
            type="file"
            multiple
            accept="image/*"
            className="hidden"
            onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
              addFiles(Array.from(e.target.files || []));
            }}
            id={formId}
          />
          <label htmlFor={formId} className="cursor-pointer">
            <div className="space-y-2">
              <p>拖拽图片到此处，或点击上传</p>
              {filesMapRef.current.size > 0 && (
                <span className="text-sm text-muted-foreground">
                  已选择 {filesMapRef.current.size} 张图片
                </span>
              )}
            </div>
          </label>
        </div>
        <div className="max-h-52 overflow-auto scrollbar-none space-y-3">
          {Array.from(filesMapRef.current.entries()).map(
            ([key, { file, status }]) => (
              <div
                key={file.name}
                className="flex items-center justify-between"
              >
                <div className="flex items-center gap-2">
                  <span className="i-ri-image-2-fill text-lg" />
                  <span>{file.name}</span>
                  <span
                    className="i-ri-close-line cursor-pointer"
                    onClick={() => {
                      filesMapRef.current.delete(key);
                      triggerUpdate();
                    }}
                  />
                </div>
                <span>
                  {typeof status === 'number' ? `${status}%` : status}
                </span>
              </div>
            ),
          )}
        </div>
        <div className="flex justify-between">
          <Button
            variant="outline"
            onClick={() => {
              filesMapRef.current.clear();
              for (const id of pendingTasksRef.current.values())
                cosClient?.client.cancelTask(id);
              pendingTasksRef.current.clear();
              setOpen(false);
            }}
          >
            关闭
          </Button>
          <Button
            disabled={isPending || filesMapRef.current.size === 0}
            onClick={handleUpload}
            className="flex items-center gap-3"
          >
            {isPending && <span className="animate-spin i-ri-loader-5-fill" />}
            {isPending ? '上传中' : '确认上传'}
          </Button>
        </div>
      </DialogContent>
    </Dialog>
  );
}
